﻿using System;
using System.Collections.Generic;
using System.IO;
using JetBrains.Application;
using JetBrains.CommonControls;
using JetBrains.Metadata.Reader.API;
using JetBrains.ProjectModel;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.ReSharper.TaskRunnerFramework;
using JetBrains.ReSharper.UnitTestExplorer;
using JetBrains.TreeModels;
using JetBrains.UI.TreeView;
using JetBrains.Util;
using Objectware.Silverlight.Testing.Runner.Resharper.Elements;
using Objectware.Silverlight.Testing.Runner.Resharper.Runners;
using Objectware.Silverlight.Testing.Runner.Resharper.Tasks;
using AssemblyReference = JetBrains.Metadata.Access.AssemblyReference;
using log4net;

namespace Objectware.Silverlight.Testing.Runner.Resharper
{
	[UnitTestProvider]
	public class UnitTestProvider : IUnitTestProvider
	{
		private readonly ILog logger = Logging.GetLogger<UnitTestProvider>();
		public static readonly BrowserPresenter Presenter = new BrowserPresenter();

		public UnitTestProvider()
		{
			ServerManager.HandlePhysicalWebSitePath();
		}


		public string ID
		{
			get { return "Silverlight"; }
		}

		public static bool IsTestAssembly(IMetadataAssembly assembly)
		{
			foreach (AssemblyReference reference in assembly.ReferencedAssembliesNames)
			{
				if (reference.AssemblyName.Name.ToLowerInvariant().Contains("microsoft.silverlight.testing"))
				{
					return true;
				}
			}
			return false;
		}



		private void ExploreTestClass(IMetadataAssembly assembly,
							  IMetadataTypeInfo type,
							  IEnumerable<IMetadataMethod> methods,
							  IProjectModelElement project,
							  UnitTestElementConsumer consumer)
		{
			ClassTestElement @class = new ClassTestElement(this, project, type.FullyQualifiedName, assembly.Location);
			@class.SetExplicit(TypeUtility.GetExplicitStringIfIgnored(type));
			consumer(@class);

			int order = 0;

			foreach (IMetadataMethod method in methods)
			{
				if (TypeUtility.IsTestMethod(method))
				{
					ExploreTestMethod(project, @class, consumer, method, order++);
				}
			}
		}

		private void ExploreTestMethod(IProjectModelElement project,
							   ClassTestElement @class,
							   UnitTestElementConsumer consumer,
							   IMetadataMethod method,
							   int order)
		{
			MethodTestElement test = new MethodTestElement(this, @class, project, method.DeclaringType.FullyQualifiedName, method.Name, order);
			test.SetExplicit(TypeUtility.GetExplicitStringIfIgnored(method));
			consumer(test);
		}


		public void ExploreAssembly(IMetadataAssembly assembly, IProject project, UnitTestElementConsumer consumer)
		{
			if (!IsTestAssembly(assembly))
			{
				return;
			}

			foreach (IMetadataTypeInfo typeInfo in assembly.GetTypes())
			{
				if (TypeUtility.IsTestClass(typeInfo))
				{
					IMetadataMethod[] methods = typeInfo.GetMethods();
					this.ExploreTestClass(assembly, typeInfo, methods, project, consumer);
				}
			}
		}

		public void ExploreFile(IFile psiFile, UnitTestElementLocationConsumer consumer, CheckForInterrupt interrupted)
		{
			psiFile.ProcessDescendants(new FileExplorer(this, consumer, psiFile, interrupted));
		}

		public bool IsUnitTestElement(IDeclaredElement element)
		{
			if (element is IClass && TypeUtility.IsTestClass(element as IClass))
			{
				return true;
			}
			if (element is IMethod && TypeUtility.IsTestMethod(element as IMethod))
			{
				return true;
			}

			return false;
		}

		public void Present(UnitTestElement element, IPresentableItem item, TreeModelNode node, PresentationState state)
		{
			Presenter.UpdateItem(element, node, item, state);
		}

		public RemoteTaskRunnerInfo GetTaskRunnerInfo()
		{
			RemoteTaskRunnerInfo runnerInfo = new RemoteTaskRunnerInfo(typeof(SilverlightRunner));
			return runnerInfo;
		}


		public IList<UnitTestTask> GetTaskSequence(UnitTestElement element, IList<UnitTestElement> explicitElements)
		{
			MethodTestElement testMethod = element as MethodTestElement;

			if (testMethod != null)
			{
				ClassTestElement testClass = testMethod.Class;
				List<UnitTestTask> result = new List<UnitTestTask>();

				result.Add(new UnitTestTask(null,
											new AssemblyLoadTask(testClass.AssemblyLocation)));

				result.Add(new UnitTestTask(testClass,
											new ClassTask(testClass.AssemblyLocation,
																   testClass.GetTypeClrName(),
																   explicitElements.Contains(testClass))));

				string path = Path.GetDirectoryName(testMethod.Class.AssemblyLocation);
				string projectName = Path.GetFileNameWithoutExtension(testMethod.Class.AssemblyLocation);
				string xapFile = string.Format("{0}\\{1}.xap", path, projectName);

				
				result.Add(new UnitTestTask(testMethod,
											new MethodTask(testClass.GetTypeClrName(),
															testMethod.MethodName,
															testMethod,
															explicitElements.Contains(testMethod),
															path,
															xapFile)));
				return result;
			}

			if (element is ClassTestElement)
			{
				return EmptyArray<UnitTestTask>.Instance;
			}

			throw new ArgumentException(string.Format("element is not xUnit: '{0}'", element));
		}

		public int CompareUnitTestElements(UnitTestElement x, UnitTestElement y)
		{
			if (Equals(x, y))
				return 0;

			int compare = StringComparer.CurrentCultureIgnoreCase.Compare(x.GetTypeClrName(), y.GetTypeClrName());
			if (compare != 0)
				return compare;

			if (x is MethodTestElement && y is ClassTestElement)
				return -1;

			if (x is ClassTestElement && y is MethodTestElement)
				return 1;

			if (x is ClassTestElement && y is ClassTestElement)
				return 0;

			MethodTestElement xe = (MethodTestElement)x;
			MethodTestElement ye = (MethodTestElement)y;
			return xe.Order.CompareTo(ye.Order);
		}


		#region Not implemented

		public void ProfferConfiguration(TaskExecutorConfiguration configuration, UnitTestSession session) { }
		public void ExploreExternal(UnitTestElementConsumer consumer) { }
		public void ExploreSolution(ISolution solution, UnitTestElementConsumer consumer) { }
		public string Serialize(UnitTestElement element) { return null; }
		public UnitTestElement Deserialize(ISolution solution, string elementString) { return null; }

		#endregion


	}
}
